﻿using System;
using System.Collections.Generic;
using System.DirectoryServices;
using Eff.Windows.ActiveDirectory.Enum;

namespace Eff.Windows.ActiveDirectory
{
    /// <summary>
    /// 活动目录辅助类。封装一系列活动目录操作相关的方法。
    /// </summary>
    public class ADHelper
    {
        //        /// <summary>
        //        /// 域名,eg. ad.test.com
        //        /// </summary>
        //        private static readonly string DomainName = ConfigurationManager.AppSettings["DomainName"];
        //        ///
        //        /// LDAP 地址
        //        ///
        //        // private static string LDAPDomain = "DC=MyDomain,DC=local";
        //
        //        /// <summary>
        //        /// LDAP绑定路径,eg. "LDAP://WIN-GBVJJ6LTPNB"
        //        /// </summary>
        //        private static readonly string ADPath = ConfigurationManager.AppSettings["ADPath"];
        //
        //        /// <summary>
        //        /// ad管理员登录帐号
        //        /// </summary>
        //        private static readonly string ADAdmin = ConfigurationManager.AppSettings["ADAdmin"];
        //
        //        /// <summary>
        //        /// ad管理员登录密码
        //        /// </summary>
        //        private static readonly string ADAdminPwd = ConfigurationManager.AppSettings["ADAdminPwd"];


        /// <summary>
        /// 域名,eg. ad.test.com
        /// </summary>
        private readonly string _domainName;
        ///
        /// LDAP 地址
        ///
        // private static string LDAPDomain = "DC=MyDomain,DC=local";

        /// <summary>
        /// LDAP绑定路径,eg. "LDAP://WIN-GBVJJ6LTPNB"
        /// </summary>
        private readonly string _adPath;

        /// <summary>
        /// ad管理员登录帐号
        /// </summary>
        private readonly string _adAdmin;

        /// <summary>
        /// ad管理员登录密码
        /// </summary>
        private readonly string _adAdminPwd;
        /// <summary>
        /// 扮演类实例
        /// </summary>
        private readonly IdentityImpersonation _impersonate;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="adPath">LDAP绑定路径,eg. "LDAP://WIN-GBVJJ6LTPNB"</param>
        /// <param name="domainName">域名,eg. ad.test.com</param>
        /// <param name="adAdmin">ad管理员登录帐号</param>
        /// <param name="adAdminPwd">ad管理员登录密码</param>
        public ADHelper(string adPath, string domainName, string adAdmin, string adAdminPwd)
        {
            _adPath = adPath;
            _domainName = domainName;
            _adAdmin = adAdmin;
            _adAdminPwd = adAdminPwd;
            _impersonate = new IdentityImpersonation(_adAdmin, _adAdminPwd, _domainName);

        }

        #region GetDirectoryObject

        /// <summary>
        /// 获得DirectoryEntry对象实例,以管理员登陆AD
        /// </summary>
        /// <returns></returns>
        private DirectoryEntry GetDirectoryObjectByAdmin()
        {
            var entry = new DirectoryEntry(_adPath, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
            return entry;
        }

        /// <summary>
        /// 根据指定用户名和密码获得相应DirectoryEntry实体
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        private DirectoryEntry GetDirectoryObject(string userName, string password)
        {
            var entry = new DirectoryEntry(_adPath, userName, password, AuthenticationTypes.Secure);
            return entry;
        }

        /// <summary>
        /// i.e. /CN=Users,DC=creditsights, DC=cyberelves, DC=Com
        /// </summary>
        /// <param name="domainReference"></param>
        /// <returns></returns>
        private DirectoryEntry GetDirectoryObject(string domainReference)
        {
            var entry = new DirectoryEntry(_adPath + domainReference, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
            return entry;
        }

        /// <summary>
        /// 获得以UserName,Password创建的DirectoryEntry
        /// </summary>
        /// <param name="domainReference"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        private DirectoryEntry GetDirectoryObject(string domainReference, string userName, string password)
        {
            var entry = new DirectoryEntry(_adPath + domainReference, userName, password, AuthenticationTypes.Secure);
            return entry;
        }

        #endregion

        #region GetDirectoryEntry

        /// <summary>
        /// 根据用户公共名称取得用户的 对象
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <param name="de"></param>
        /// <returns>如果找到该用户，则返回用户的 对象；否则返回 null</returns>
        public DirectoryEntry GetDirectoryEntry(string commonName, DirectoryEntry de = null)
        {
            if (de == null)
                de = GetDirectoryObjectByAdmin();
            var deSearch = new DirectorySearcher(de)
                               {
                                   Filter = "(&(&(objectCategory=person)(objectClass=user))(cn=" + commonName + "))",
                                   SearchScope = SearchScope.Subtree
                               };

            try
            {
                SearchResult result = deSearch.FindOne();
                de = new DirectoryEntry(result.Path, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
                return de;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 根据用户公共名称和密码取得用户的 对象。
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <param name="password">用户密码</param>
        /// <returns>如果找到该用户，则返回用户的 对象；否则返回 null</returns>
        public DirectoryEntry GetDirectoryEntry(string commonName, string password)
        {
            DirectoryEntry de = GetDirectoryObject(commonName, password);
            var deSearch = new DirectorySearcher(de)
                               {
                                   Filter = "(&(&(objectCategory=person)(objectClass=user))(cn=" + commonName + "))",
                                   SearchScope = SearchScope.Subtree
                               };

            try
            {
                SearchResult result = deSearch.FindOne();
                de = new DirectoryEntry(result.Path, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
                return de;
            }
            catch
            {
                return null;
            }
        }


        /// <summary>
        /// 根据用户帐号称取得用户的 对象
        /// </summary>
        /// <param name="sAMAccountName">用户帐号名</param>
        /// <returns>如果找到该用户，则返回用户的 对象；否则返回 null</returns>
        public DirectoryEntry GetDirectoryEntryByAccount(string sAMAccountName)
        {
            DirectoryEntry de = GetDirectoryObjectByAdmin();
            var deSearch = new DirectorySearcher(de)
                               {
                                   Filter =
                                       "(&(&(objectCategory=person)(objectClass=user))(sAMAccountName=" + sAMAccountName +
                                       "))",
                                   SearchScope = SearchScope.Subtree
                               };

            try
            {
                SearchResult result = deSearch.FindOne();
                de = new DirectoryEntry(result.Path, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
                return de;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 根据用户帐号和密码取得用户的 对象
        /// </summary>
        /// <param name="sAMAccountName">用户帐号名</param>
        /// <param name="password">用户密码</param>
        /// <returns>如果找到该用户，则返回用户的 对象；否则返回 null</returns>
        public DirectoryEntry GetDirectoryEntryByAccount(string sAMAccountName, string password)
        {
            DirectoryEntry de = GetDirectoryEntryByAccount(sAMAccountName);
            if (de != null)
            {
                string commonName = de.Properties["cn"][0].ToString();

                if (GetDirectoryEntry(commonName, password) != null)
                    return GetDirectoryEntry(commonName, password);
                return null;
            }
            return null;
        }

        /// <summary>
        /// 根据组名取得用户组的 下所有用户
        /// </summary>
        /// <returns></returns>
        public List<string> GetUserListFromGroup(string groupName)
        {
            DirectoryEntry de = GetDirectoryEntryOfGroup(groupName);
            var list = new List<string>(de.Properties["member"].Count);
            foreach (var item in de.Properties["member"])
            {
                var deUser = new DirectoryEntry(_adPath + "/" + item, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
                list.Add(GetProperty(deUser, "sAMAccountName").Value.ToString());
                //populate the column
                //                rwUser["UserName"] = GetProperty(deUser, "cn");
                //                rwUser["DisplayName"] = GetProperty(deUser, "givenName") + " " + GetProperty(deUser, "sn");
                //                rwUser["EMailAddress"] = GetProperty(deUser, "mail");

            }
            return list;

        }

        /// <summary>
        /// 根据组名取得用户组的 对象
        /// </summary>
        /// <param name="groupName">组名</param>
        /// <returns></returns>
        public DirectoryEntry GetDirectoryEntryOfGroup(string groupName)
        {
            DirectoryEntry de = GetDirectoryObjectByAdmin();
            var deSearch = new DirectorySearcher(de)
                               {
                                   Filter = "(&(objectClass=group)(cn=" + groupName + "))",
                                   SearchScope = SearchScope.Subtree
                               };

            try
            {
                SearchResult result = deSearch.FindOne();
                de = new DirectoryEntry(result.Path, _adAdmin, _adAdminPwd, AuthenticationTypes.Secure);
                return de;
            }
            catch
            {
                return null;
            }
        }


        #endregion

        #region Property

        /// <summary>
        /// 获得指定 指定属性名对应的值
        /// </summary>
        /// <param name="de"></param>
        /// <param name="propertyName">属性名称</param>
        /// <returns>属性值</returns>
        public PropertyValueCollection GetProperty(DirectoryEntry de, string propertyName)
        {
            if (de.Properties.Contains(propertyName))
            {
                return de.Properties[propertyName];
            }
            return null;
        }

        /// <summary>
        /// 获得指定搜索结果 中指定属性名对应的值
        /// </summary>
        /// <param name="searchResult"></param>
        /// <param name="propertyName">属性名称</param>
        /// <returns>属性值</returns>
        public string GetProperty(SearchResult searchResult, string propertyName)
        {
            if (searchResult.Properties.Contains(propertyName))
            {
                return searchResult.Properties[propertyName][0].ToString();
            }
            return string.Empty;
        }

        /// <summary>
        /// 设置指定 的属性值
        /// </summary>
        /// <param name="de"></param>
        /// <param name="propertyName">属性名称</param>
        /// <param name="propertyValue">属性值</param>
        public void SetProperty(DirectoryEntry de, string propertyName, string propertyValue)
        {
            if (!string.IsNullOrEmpty(propertyValue))
            {
                if (de.Properties.Contains(propertyName))
                {
                    de.Properties[propertyName][0] = propertyValue;
                }
                else
                {
                    de.Properties[propertyName].Add(propertyValue);
                }
            }
        }
        #endregion

        /// <summary>
        /// 创建新的用户
        /// </summary>
        /// <param name="ldapDN">DN 位置。例如：OU=共享平台 或 CN=Users</param>
        /// <param name="commonName">公共名称</param>
        /// <param name="sAMAccountName">帐号</param>
        /// <param name="password">密码</param>
        /// <param name="emailAddress">邮箱</param>
        /// <returns></returns>
        public DirectoryEntry CreateNewUser(string ldapDN, string commonName, string sAMAccountName, string password, string emailAddress = "")
        {
            DirectoryEntry entry = GetDirectoryObjectByAdmin();
            DirectoryEntry subEntry = entry.Children.Find(ldapDN);
            DirectoryEntry deUser = subEntry.Children.Add("CN=" + commonName, "user");
            deUser.Properties["sAMAccountName"].Value = sAMAccountName;
            deUser.Properties["userPrincipalName"].Value = sAMAccountName + "@" + _domainName;
            if (!string.IsNullOrEmpty(emailAddress))
                deUser.Properties["Mail"].Value = emailAddress;
            deUser.CommitChanges();
            SetPassword(commonName, password);
            EnableUser(deUser);

            deUser.Close();
            return deUser;
        }

        /////
        ///// 创建新的用户。默认创建在 Users 单元下。
        /////
        ///// 公共名称
        ///// 帐号
        ///// 密码
        /////
        //public static DirectoryEntry CreateNewUser(string commonName, string sAMAccountName, string password)
        //{
        //    return CreateNewUser("CN=Users", commonName, sAMAccountName, password);
        //}

        /// <summary>
        /// 创建新的用户
        /// </summary>
        /// <param name="sAMAccountName">帐号</param>
        /// <param name="password">密码</param>
        /// <param name="emailAddress">邮箱</param>
        /// <returns></returns>
        public DirectoryEntry CreateNewUser(string sAMAccountName, string password, string emailAddress)
        {
            return CreateNewUser("CN=Users", sAMAccountName, sAMAccountName, password, emailAddress);
        }

        /// <summary>
        /// 判断指定公共名称的用户是否存在
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <returns>如果存在，返回 true；否则返回 false</returns>
        public bool IsUserExists(string commonName)
        {
            DirectoryEntry de = GetDirectoryObjectByAdmin();
            var deSearch = new DirectorySearcher(de) { Filter = "(&(&(objectCategory=person)(objectClass=user))(cn=" + commonName + "))" };
            // LDAP 查询串
            SearchResultCollection results = deSearch.FindAll();

            if (results.Count == 0)
                return false;
            return true;
        }

        /// <summary>
        /// 判断指定用户名的用户是否存在
        /// </summary>
        /// <param name="sAMAccountName">用户名</param>
        /// <returns>如果存在，返回 true；否则返回 false</returns>
        public bool IsUserExistsByAccount(string sAMAccountName)
        {
            DirectoryEntry de = GetDirectoryObjectByAdmin();
            var deSearch = new DirectorySearcher(de) { Filter = "(&(&(objectCategory=person)(objectClass=user))(sAMAccountName=" + sAMAccountName + "))" };
            // LDAP 查询串
            SearchResultCollection results = deSearch.FindAll();

            if (results.Count == 0)
                return false;
            return true;
        }


        /// <summary>
        /// 判断用户帐号是否激活
        /// </summary>
        /// <param name="userAccountControl">用户帐号属性控制器</param>
        /// <returns>如果用户帐号已经激活，返回 true；否则返回 false</returns>
        public bool IsAccountActive(int userAccountControl)
        {
            int userAccountControlDisabled = Convert.ToInt32(AdsUserFlagEnum.ADS_UF_ACCOUNTDISABLE);
            int flagExists = userAccountControl & userAccountControlDisabled;

            if (flagExists > 0)
                return false;
            return true;
        }

        /// <summary>
        /// 判断用户与密码是否足够以满足身份验证进而登录
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public LoginResult Login(string commonName, string password)
        {
            DirectoryEntry de = GetDirectoryEntry(commonName);

            if (de != null)
            {
                // 必须在判断用户密码正确前，对帐号激活属性进行判断；否则将出现异常。
                //   int userAccountControl = Convert.ToInt32(de.Properties["userAccountControl"][0]);
                //   de.Close();

                //  if (!IsAccountActive(userAccountControl))
                //     return LoginResult.LOGIN_USER_ACCOUNT_INACTIVE;

                if (GetDirectoryEntry(commonName, password) != null)
                    return LoginResult.Ok;

                return LoginResult.PasswordIncorrect;
            }
            return LoginResult.NotExist;
        }

        /// <summary>
        /// 判断用户帐号与密码是否足够以满足身份验证进而登录
        /// </summary>
        /// <param name="sAMAccountName">用户帐号</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public LoginResult LoginByAccount(string sAMAccountName, string password)
        {
            DirectoryEntry de = GetDirectoryEntryByAccount(sAMAccountName);

            if (de != null)
            {
                // 必须在判断用户密码正确前，对帐号激活属性进行判断；否则将出现异常。
                //                int userAccountControl = Convert.ToInt32(de.Properties["userAccountControl"][0]);
                //                de.Close();

                //                if (!IsAccountActive(userAccountControl))
                //                    return LoginResult.Inactive;

                if (GetDirectoryEntryByAccount(sAMAccountName, password) != null)
                    return LoginResult.Ok;
                return LoginResult.PasswordIncorrect;
            }
            return LoginResult.NotExist;
        }


        /// <summary>
        /// 设置密码（模拟管理员权限）
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <param name="newPassword">新密码</param>
        public void SetPassword(string commonName, string newPassword)
        {
            DirectoryEntry de = GetDirectoryEntry(commonName);

            // 模拟超级管理员，以达到有权限修改用户密码
            _impersonate.BeginImpersonate();
            log4net.LogManager.GetLogger("t").Debug(de.Properties["cn"].Value);
            de.Invoke("SetPassword", new object[] { newPassword });
            _impersonate.StopImpersonate();

            de.Close();
        }

        /// <summary>
        /// 设置帐号密码，管理员可以通过它来修改指定帐号的密码。
        /// 注意：！！这里需要用到模拟管理员账户，修改web.config把&lt;identity impersonate = "true" &gt;去掉
        /// </summary>
        /// <param name="sAMAccountName">用户帐号</param>
        /// <param name="newPassword">用户新密码</param>
        public void SetPasswordByAccount(string sAMAccountName, string newPassword)
        {
            DirectoryEntry de = GetDirectoryEntryByAccount(sAMAccountName);

            // 模拟超级管理员，以达到有权限修改用户密码
            var impersonate = new IdentityImpersonation(_adAdmin, _adAdminPwd, _domainName);
            impersonate.BeginImpersonate();
            de.Invoke("SetPassword", new object[] { newPassword });
            impersonate.StopImpersonate();

            de.Close();
        }

        /// <summary>
        /// 修改用户密码
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        /// <param name="oldPassword">旧密码</param>
        /// <param name="newPassword">新密码</param>
        public bool ChangeUserPassword(string commonName, string oldPassword, string newPassword)
        {
            if (Login(commonName, oldPassword) == LoginResult.Ok)
            {
                SetPassword(commonName, newPassword);
                return true;
            }
            return false;
            // to-do: 需要解决密码策略问题
            DirectoryEntry oUser = GetDirectoryEntry(commonName);
            oUser.Invoke("ChangePassword", oldPassword, newPassword);
            oUser.Close();
        }

        /// <summary>
        /// 启用指定公共名称的用户
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        public void EnableUser(string commonName)
        {
            EnableUser(GetDirectoryEntry(commonName));
        }

        /// <summary>
        /// 启用指定 的用户
        /// </summary>
        /// <param name="de"></param>
        private void EnableUser(DirectoryEntry de)
        {
            _impersonate.BeginImpersonate();
            de.Properties["userAccountControl"][0] = AdsUserFlagEnum.ADS_UF_NORMAL_ACCOUNT | AdsUserFlagEnum.ADS_UF_DONT_EXPIRE_PASSWD;
            de.CommitChanges();
            _impersonate.StopImpersonate();
            de.Close();
        }

        /// <summary>
        /// 禁用指定公共名称的用户
        /// </summary>
        /// <param name="commonName">用户公共名称</param>
        public void DisableUser(string commonName)
        {
            DisableUser(GetDirectoryEntry(commonName));
        }

        /// <summary>
        /// 禁用指定公共名称的用户
        /// </summary>
        /// <param name="de"></param>
        private void DisableUser(DirectoryEntry de)
        {
            _impersonate.BeginImpersonate();
            de.Properties["userAccountControl"][0] = AdsUserFlagEnum.ADS_UF_NORMAL_ACCOUNT | AdsUserFlagEnum.ADS_UF_DONT_EXPIRE_PASSWD | AdsUserFlagEnum.ADS_UF_ACCOUNTDISABLE;
            de.CommitChanges();
            _impersonate.StopImpersonate();
            de.Close();
        }

        private bool IsUserExistsInGroup(string userCommonName, string groupName)
        {
            DirectoryEntry oGroup = GetDirectoryEntryOfGroup(groupName);
            var member = GetProperty(oGroup, "member");
            if (member == null) return false;
            foreach (string propertyValue in GetProperty(oGroup, "member"))
            {
                if (propertyValue.IndexOf("CN=" + userCommonName, StringComparison.OrdinalIgnoreCase) > -1) return true;
            }

            return false;
        }

        /// <summary>
        /// 将指定的用户添加到指定的组中。默认为 Users 下的组和用户。
        /// </summary>
        /// <param name="userCommonName">用户公共名称</param>
        /// <param name="groupName"> 组名</param>
        public void AddUserToGroup(string userCommonName, string groupName)
        {
            if (IsUserExistsInGroup(userCommonName, groupName)) return;
            DirectoryEntry oGroup = GetDirectoryEntryOfGroup(groupName);
            DirectoryEntry oUser = GetDirectoryEntry(userCommonName);

            _impersonate.BeginImpersonate();
            oGroup.Properties["member"].Add(oUser.Properties["distinguishedName"].Value);
            oGroup.CommitChanges();
            _impersonate.StopImpersonate();

            oGroup.Close();
            oUser.Close();
        }

        /// <summary>
        /// 将用户从指定组中移除。默认为 Users 下的组和用户。
        /// </summary>
        /// <param name="userCommonName"> 用户公共名称</param>
        /// <param name="groupName">组名</param>
        public void RemoveUserFromGroup(string userCommonName, string groupName)
        {
            DirectoryEntry oGroup = GetDirectoryEntryOfGroup(groupName);
            DirectoryEntry oUser = GetDirectoryEntry(userCommonName);

            _impersonate.BeginImpersonate();
            oGroup.Properties["member"].Remove(oUser.Properties["distinguishedName"].Value);
            oGroup.CommitChanges();
            _impersonate.StopImpersonate();

            oGroup.Close();
            oUser.Close();
        }
    }
}
